package com.xiashitech.agent.instrumentation.biz.api.increment.inst;

import com.xiashitech.agent.cache.status.CacheStatus;
import com.xiashitech.agent.config.StartConfig;
import com.xiashitech.agent.instrumentation.biz.api.increment.ApiIncrementHelper;
import com.xiashitech.agent.instrumentation.biz.userinfo.httpsession.SessionData;
import com.xiashitech.agent.instrumentation.spring.controller.apicapture.utils.ApiSyncUtil;
import com.xiashitech.agent.utils.DurationPrinter;
import com.xiashitech.agent.utils.XSUtil;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.shaded.io.opentelemetry.context.Context;
import io.opentelemetry.javaagent.shaded.io.opentelemetry.context.Scope;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import org.springframework.web.context.request.ServletWebRequest;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static net.bytebuddy.matcher.ElementMatchers.*;

public class XSApiIncrementSpringInstrumentation implements TypeInstrumentation {

    @Override
    public ElementMatcher<ClassLoader> classLoaderOptimization() {
        return hasClassesNamed("org.springframework.web.servlet.DispatcherServlet");
    }

    @Override
    public ElementMatcher<TypeDescription> typeMatcher() {
        //这个插桩点能最大限度兼容所有spring版本。但这个点位获取不到对应Controller的元数据。
        //通过缓存全量API信息来弥补这个问题。判断是否为Map等特殊出入参通过从缓存中取出API元信息来实现。
        
        return named("org.springframework.web.servlet.DispatcherServlet");
    }

    @Override
    public void transform(TypeTransformer typeTransformer) {
        typeTransformer.applyAdviceToMethod(
                isMethod().and(named("doService"))
                        .and(takesArgument(0, named("javax.servlet.http.HttpServletRequest")))
                        .and(takesArgument(1, named("javax.servlet.http.HttpServletResponse")))
                        .and(isProtected()),
                this.getClass().getName() + "$DoAdvice");
    }

    public static class DoAdvice {

        @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
        public static void onExit(@Advice.Argument(value = 0, readOnly = false) HttpServletRequest request,
                                  @Advice.Argument(value = 1, readOnly = false) HttpServletResponse response,
                                  @Advice.Local("otelContext") Context context,
                                  @Advice.Local("otelScope") Scope scope) {
            DurationPrinter durationPrinter = new DurationPrinter("XSApiIncrementSpringInstrumentation");
            try {
                if (CacheStatus.cannotRun()) {
                    return;
                }
                ApiIncrementHelper.syncIncrementApi(request,response);
            } catch (Throwable e) {
                StartConfig.throwableStackPrinter(e);
            }
            durationPrinter.printElapseDuration("DoAdvice e");
        }

    }
}
